<template>
  <!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸，所以在外面套一个"壳" -->
  <view>
    <view class="u-index-bar">
      <slot />
      <view
        v-if="showSidebar"
        class="u-index-bar__sidebar"
        @touchstart.stop.prevent="onTouchMove"
        @touchmove.stop.prevent="onTouchMove"
        @touchend.stop.prevent="onTouchStop"
        @touchcancel.stop.prevent="onTouchStop"
        :style="'top:' + alertTop"
      >
        <view
          v-for="(item, index) in indexList"
          :key="index"
          class="u-index-bar__index"
          :style="{
            zIndex: zIndex + 1,
            color: activeAnchorIndex === index ? activeColor : '',
          }"
          :data-index="index"
        >
          {{ item }}
        </view>
      </view>
      <view
        class="u-indexed-list-alert"
        v-if="touchmove && indexList[touchmoveIndex]"
        :style="{
          zIndex: alertZIndex,
        }"
      >
        <text>{{ indexList[touchmoveIndex] }}</text>
      </view>
    </view>
  </view>
</template>

<script>
var indexList = function () {
  var indexList = [];
  var charCodeOfA = "A".charCodeAt(0);
  for (var i = 0; i < 26; i++) {
    indexList.push(String.fromCharCode(charCodeOfA + i));
  }
  return indexList;
};

/**
 * indexList 索引列表
 * @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用
 * @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
 * @property {Number String} scroll-top 当前滚动高度，自定义组件无法获得滚动条事件，所以依赖接入方传入
 * @property {Array} index-list 索引字符列表，数组（默认A-Z）
 * @property {Number String} z-index 锚点吸顶时的层级（默认965）
 * @property {Boolean} sticky 是否开启锚点自动吸顶（默认true）
 * @property {Number String} offset-top 锚点自动吸顶时与顶部的距离（默认0）
 * @property {String} highlight-color 锚点和右边索引字符高亮颜色（默认#2979ff）
 * @event {Function} select 选中右边索引字符时触发
 * @example <u-index-list :scrollTop="scrollTop"></u-index-list>
 */
export default {
  name: "u-index-list",
  props: {
    sticky: {
      type: Boolean,
      default: true,
    },
    zIndex: {
      type: [Number, String],
      default: "",
    },
    scrollTop: {
      type: [Number, String],
      default: 0,
    },
    offsetTop: {
      type: [Number, String],
      default: 0,
    },
    indexList: {
      type: Array,
      default() {
        return indexList();
      },
    },
    activeColor: {
      type: String,
      default: "#2979ff",
    },
    alertTop: {
      type: String,
      default: "50%",
    },
  },
  created() {
    // #ifdef H5
    this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44;
    // #endif
    // #ifndef H5
    this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0;
    // #endif
    // 只能在created生命周期定义children，如果在data定义，会因为循环引用而报错
    this.children = [];
  },
  data() {
    return {
      activeAnchorIndex: 0,
      showSidebar: true,
      // children: [],
      touchmove: false,
      touchmoveIndex: 0,
    };
  },
  watch: {
    scrollTop() {
      this.updateData();
    },
  },
  computed: {
    // 弹出toast的z-index值
    alertZIndex() {
      return this.$u.zIndex.toast;
    },
  },
  methods: {
    updateData() {
      this.timer && clearTimeout(this.timer);
      this.timer = setTimeout(() => {
        this.showSidebar = !!this.children.length;
        this.setRect().then(() => {
          this.onScroll();
        });
      }, 0);
    },
    setRect() {
      return Promise.all([
        this.setAnchorsRect(),
        this.setListRect(),
        this.setSiderbarRect(),
      ]);
    },
    setAnchorsRect() {
      return Promise.all(
        this.children.map((anchor, index) =>
          anchor.$uGetRect(".u-index-anchor-wrapper").then((rect) => {
            Object.assign(anchor, {
              height: rect.height,
              top: rect.top,
            });
          })
        )
      );
    },
    setListRect() {
      return this.$uGetRect(".u-index-bar").then((rect) => {
        Object.assign(this, {
          height: rect.height,
          top: rect.top + this.scrollTop,
        });
      });
    },
    setSiderbarRect() {
      return this.$uGetRect(".u-index-bar__sidebar").then((rect) => {
        this.sidebar = {
          height: rect.height,
          top: rect.top,
        };
      });
    },
    getActiveAnchorIndex() {
      const { children } = this;
      const { sticky } = this;
      for (let i = this.children.length - 1; i >= 0; i--) {
        const preAnchorHeight = i > 0 ? children[i - 1].height : 0;
        const reachTop = sticky ? preAnchorHeight : 0;
        if (reachTop >= children[i].top) {
          return i;
        }
      }
      return -1;
    },
    onScroll() {
      const { children = [] } = this;
      if (!children.length) {
        return;
      }
      const { sticky, stickyOffsetTop, zIndex, scrollTop, activeColor } = this;
      const active = this.getActiveAnchorIndex();
      this.activeAnchorIndex = active;
      if (sticky) {
        let isActiveAnchorSticky = false;
        if (active !== -1) {
          isActiveAnchorSticky = children[active].top <= 0;
        }
        children.forEach((item, index) => {
          if (index === active) {
            let wrapperStyle = "";
            let anchorStyle = {
              color: `${activeColor}`,
            };
            if (isActiveAnchorSticky) {
              wrapperStyle = {
                height: `${children[index].height}px`,
              };
              anchorStyle = {
                position: "fixed",
                top: `${stickyOffsetTop}px`,
                zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
                color: `${activeColor}`,
              };
            }
            item.active = active;
            item.wrapperStyle = wrapperStyle;
            item.anchorStyle = anchorStyle;
          } else if (index === active - 1) {
            const currentAnchor = children[index];
            const currentOffsetTop = currentAnchor.top;
            const targetOffsetTop =
              index === children.length - 1
                ? this.top
                : children[index + 1].top;
            const parentOffsetHeight = targetOffsetTop - currentOffsetTop;
            const translateY = parentOffsetHeight - currentAnchor.height;
            const anchorStyle = {
              position: "relative",
              transform: `translate3d(0, ${translateY}px, 0)`,
              zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
              color: `${activeColor}`,
            };
            item.active = active;
            item.anchorStyle = anchorStyle;
          } else {
            item.active = false;
            item.anchorStyle = "";
            item.wrapperStyle = "";
          }
        });
      }
    },
    onTouchMove(event) {
      this.touchmove = true;
      const sidebarLength = this.children.length;
      const touch = event.touches[0];
      const itemHeight = this.sidebar.height / sidebarLength;
      let clientY = 0;
      clientY = touch.clientY;
      let index = Math.floor((clientY - this.sidebar.top) / itemHeight);
      if (index < 0) {
        index = 0;
      } else if (index > sidebarLength - 1) {
        index = sidebarLength - 1;
      }
      this.touchmoveIndex = index;
      this.scrollToAnchor(index);
    },
    onTouchStop() {
      this.touchmove = false;
      this.scrollToAnchorIndex = null;
    },
    scrollToAnchor(index) {
      if (this.scrollToAnchorIndex === index) {
        return;
      }
      this.scrollToAnchorIndex = index;
      const anchor = this.children.find(
        (item) => item.index === this.indexList[index]
      );
      if (anchor) {
        this.$emit("select", anchor.index);
        uni.pageScrollTo({
          duration: 0,
          scrollTop: anchor.top + this.scrollTop,
        });
      }
    },
  },
};
</script>

<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";

.u-index-bar {
  position: relative;
}

.u-index-bar__sidebar {
  position: fixed;
  top: 50%;
  right: 0;
  @include vue-flex;
  flex-direction: column;
  text-align: center;
  transform: translateY(-50%);
  user-select: none;
  z-index: 99;
}

.u-index-bar__index {
  font-weight: 500;
  padding: 8rpx 18rpx;
  font-size: 22rpx;
  line-height: 1;
}

.u-indexed-list-alert {
  position: fixed;
  width: 120rpx;
  height: 120rpx;
  right: 90rpx;
  top: 50%;
  margin-top: -60rpx;
  border-radius: 24rpx;
  font-size: 50rpx;
  color: #fff;
  background-color: rgba(0, 0, 0, 0.65);
  @include vue-flex;
  justify-content: center;
  align-items: center;
  padding: 0;
  z-index: 9999999;
}

.u-indexed-list-alert text {
  line-height: 50rpx;
}
</style>
